﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Linq;
using log4net;
using System.Data.Linq.SqlClient;
using UPNP.IO;

namespace RouterControl.Db
{
	public class MaxSpecDom : IDisposable
	{
		private readonly ILog _log = LogManager.GetLogger(typeof(MaxSpecDom));
		private DeviceDomDb _maxSpecDom;
		private DeviceDomDbDataContext _ctx;

		public void IntegrateUnparsedDeviceDoms()
		{
			try
			{
				// retrieve
				var domDbs = (
					from d in _ctx.DeviceDomDbs
					where !d.MaxSpecParsed 
					&& d.DomType != DeviceDomTypeDb.MaxSpec
					&& SqlMethods.Like(d.RootDevice.DeviceTypeId, _maxSpecDom.RootDevice.DeviceTypeId)
					select d);

				// parse
				var total = domDbs.Count();
				var counter = 0;

				_log.Info("Device DOMs to integrate: " + total);

				foreach (var d in domDbs)
				{
					try
					{
						// integrate into devices
						IntegrateDevice(_maxSpecDom.RootDevice, d.RootDevice);

						// finalize
						d.MaxSpecParsed = true;

						_ctx.SubmitChanges();

						_log.InfoFormat(
							"Integrated device DOM ({0}/{1}): {2}",
							++counter,
							total,
							d.SchemaId
							);
					}
					catch (Exception e)
					{
						_log.Error("Failed to integrate device DOM: " + d.SchemaId, e);
					}
				}
			}
			catch (Exception e)
			{
				_log.Error("Failed to handle unparsed device DOMs", e);

				throw;
			}
		}

		private void IntegrateDevice(DeviceDb target, DeviceDb source)
		{
			// integrate copy services
			foreach (var s in source.Services)
			{
				var existing = (
					from st in target.Services
					where st.ServiceTypeId.Equals(s.ServiceTypeId, StringComparison.InvariantCultureIgnoreCase)
					select st
					).Take(1).FirstOrDefault();

				if (existing == null)
				{
					existing = s.Clone();

					existing.ServiceName = null;	// clear names from specs

					target.Services.Add(existing);
				}

				// integrate service sub-objects
				IntegrateService(existing, s);
			}

			// integrate children
			foreach (var s in source.ChildDevices)
			{
				var existing = (
					from st in target.ChildDevices
					where st.DeviceTypeId.Equals(s.DeviceTypeId, StringComparison.InvariantCultureIgnoreCase)
					select st
					).Take(1).FirstOrDefault();

				if (existing == null)
				{
					existing = s.Clone();

					existing.DeviceName = null;	// clear names from specs

					target.ChildDevices.Add(existing);
				}

				// integrate service sub-objects
				IntegrateDevice(existing, s);
			}
		}

		private void IntegrateService(ServiceDb target, ServiceDb source)
		{
			// integrate state variables
			foreach (var s in source.StateVariables)
			{
				var existing = (
					from st in target.StateVariables
					where st.VariableName.Equals(s.VariableName, StringComparison.InvariantCultureIgnoreCase)
					select st
					).Take(1).FirstOrDefault();

				if (existing == null)
				{
					existing = s.Clone();

					target.StateVariables.Add(existing);
				}

				// integrate service sub-objects
				IntegrateStateVariable(existing, s);
			}

			// integrate actions
			foreach (var s in source.Actions)
			{
				var existing = (
					from st in target.Actions
					where st.ActionName.Equals(s.ActionName, StringComparison.InvariantCultureIgnoreCase)
					select st
					).Take(1).FirstOrDefault();

				if (existing == null)
				{
					existing = s.Clone(target);

					target.Actions.Add(existing);
				}
			}
		}

		private void IntegrateStateVariable(StateVariableDb target, StateVariableDb source)
		{
			// integrate allowed values
			foreach (var s in source.AllowedValues)
			{
				var existing = (
					from st in target.AllowedValues
					where st.Value.Equals(s.Value, StringComparison.InvariantCultureIgnoreCase)
					select st
					).Take(1).FirstOrDefault();

				if (existing == null)
				{
					existing = new AllowedValueDb()
					{
						Value = s.Value
					};

					target.AllowedValues.Add(existing);
				}
			}
		}

		public static string GetSchemaId(string rootDeviceTypeId)
		{
			if (string.IsNullOrEmpty(rootDeviceTypeId))
				throw new ArgumentException("null or empty", "rootDeviceTypeId");

			return DeviceHelpers.CreateId(
				"UpnpDeviceMaximumSpec",
				rootDeviceTypeId
				);
		}

		public MaxSpecDom(string rootDeviceTypeId)
		{
			if (string.IsNullOrEmpty(rootDeviceTypeId))
				throw new ArgumentException("null or empty", "rootDeviceTypeId");

			try
			{
				// connect db
				_ctx = DeviceDomDbConnector.GetContext();
	
				// query max spec dom
				var schemaId = GetSchemaId(rootDeviceTypeId);
	
				_maxSpecDom = (
					from d in _ctx.DeviceDomDbs
					where d.DomType == DeviceDomTypeDb.MaxSpec 
					&& SqlMethods.Like(d.SchemaId, schemaId)
					select d
					).Take(1).FirstOrDefault();
	
				if (_maxSpecDom == null)
				{
					_maxSpecDom = new DeviceDomDb()
					{
						DomType = DeviceDomTypeDb.MaxSpec,
						SchemaId = schemaId
					};
	
					_ctx.DeviceDomDbs.InsertOnSubmit(_maxSpecDom);
				}
	
				// ensure root device
				if (_maxSpecDom.RootDevice == null)
				{
					_maxSpecDom.RootDevice = new DeviceDb()
					{
						DeviceTypeId = rootDeviceTypeId,
						IsRootDevice = true
					};
	
				}
	
				// flush
				_ctx.SubmitChanges();
			}
			catch (Exception e)
			{
				_log.Error("Failed to prepare max-spec device DOM", e);

				throw;
			}
		}

		public void Dispose()
		{
			if (_ctx != null)
				_ctx.Dispose();
		}

	}
}
